*** Reverse Engeneering di Babylon Translator ***

Issue #
Autore
Data creazione
Tools
Tipo protezione
1
Little-John
28/1/1999
Soft-ice 3.23

IDA 3.8

Time-bomb

Indice

-# intro #-

Babylon Translator è una utility abbastanza utile se si ha a che fare con lingue straniere, in quanto permette con un semplice click la traduzione di una parola qualsiasi in un qualsiasi documento. La versione shareware scade dopo una trentina di giorni, e siamo molto curiosi di vedere come fa il programma a capire la propria scadenza.

-# Considerazioni Generali, ovvero Pre-cracking #-

Una caratteristica dei programmi a tempo è quella di essere, generalmente, legati all'orologio di sistema. I primi programmi a tempo erano facili da bypassare, perchè bastava cambiare la data e tutto filava liscio. Poi le cose sono andate complicandosi. Infatti in alcune applicazioni cambiando la data non si faceva altro che causare la scadenza immediata (la prima applicazione di questo genere che ho incontrato personalmente era Turbo Browser '98, Dicembre '97). Ciò accade anche in Babylon Translator.

Se si cambia la data in avanti nel tempo non succcede niente, non appena la si mette un giorno indietro l'applicazione è scaduta e bisogna registrarla per forza via rete. Però metti che, come nel mio caso, uno non c'ha il modem, c'ha un bordello di traduzioni da fare per il fine settimana ed ha appena buttato il dizionario di inglese nel caminetto perchè siamo in inverno?

-# Reversing Babylon Protection - Parte I #-

Visto che Babylon è una applicazione a tempo ci sarà qualche chiamata API a riguardo. Personalmente quando mi appresto a fare ricerche del genere uso un disassemblatore, perchè ci potrebbero essere diverse funzioni API non-Windows, ma proprietarie del programma, che risiedono in file .dll. Nel nostro caso Babylon ha nella propria directory il file captlib.dll. Facendo 'Anteprima' sul file vediamo che esporta varie funzioni tra cui una un po' sospetta, OpenBabylonDLL. Ed è ora la volta di IDA. Dissassemblando il file nella funzione esportata OpenBabylonDLL c'è l'API GetsystemTime e RegOpenKeyExA come segue:

CODE:004020BA 8D 85 D4 FE FF FF lea     eax, [ebp+var_12C] ; eax = indirizzo in cui vengono
CODE:004020C0 50                push    eax		   ; salvati i dati dell'orologio
CODE:004020C1 E8 7A CC 00 00    call    j_GetSystemTime	   
CODE:004020C6 8D 55 E8          lea     edx, [ebp+var_18]
CODE:004020C9 52                push    edx
CODE:004020CA 6A 01             push    1
CODE:004020CC 6A 00             push    0
CODE:004020CE 68 8D 25 42 00    push    offset str->SoftwareBa
CODE:004020D3 68 02 00 00 80    push    80000002h
CODE:004020D8 E8 BF CD 00 00    call    j_RegOpenKeyExA
CODE:004020DD 85 C0             test    eax, eax
CODE:004020DF 0F 85 0C 02 00 00 jnz     loc_0_4022F1

Per GetsystemTime la sintassi �:

VOID GetSystemTime( LPSYSTEMTIME lpSystemTime // indirizzo della struttura 'Tempo');

quindi eax � quell'indirizzo. La struttura 'Tempo' � questa:

typedef struct _SYSTEMTIME {  // st
	WORD wYear;     <- Anno
	WORD wMonth;	<- Mese
	WORD wDayOfWeek;<- Giorno della settimana
	WORD wDay;	<- Giorno
	WORD wHour;	<- Ora
	WORD wMinute; 	<- Minuto
	WORD wSecond;	<- Secondo
	WORD wMilliseconds; } SYSTEMTIME; 

Di questa struttura nel nostro caso i dati più rilevanti saranno al massimo i primi 4. Nell'indirizzo ebp-12c nel mio caso i valori restituiti sono:

	CF 07 01 00 03 00 1B 00 ......
	|--|  |      |	   |
	1999  1	  Mercol.  27

... appunto la data odierna. Per comodità, in IDA, si può rinominare la variabile var_12c in TimeStructure.

Segue in seguito l'apertura della chiave di registro 'LOCAL\SOFTWARE\BABYLON\Babylon Translator\b1', il cui esito è eax=0, quindi la funzione ha avuto successo. Subito dopo c'è un bel RegQueryValueExA...

CODE:004020E5 C7 45 EC 08 00 00+mov     [ebp+buffer_size], 8
CODE:004020EC 8D 4D EC          lea     ecx, [ebp+buffer_size]
CODE:004020EF 51                push    ecx
CODE:004020F0 8D 85 64 FF FF FF lea     eax, [ebp+buffer_address]
CODE:004020F6 50                push    eax
CODE:004020F7 6A 00             push    0
CODE:004020F9 6A 00             push    0
CODE:004020FB 68 B4 25 42 00    push    offset str->Indexbydat
CODE:00402100 FF 75 E8          push    [ebp+Bab_Handle_of_open_HKey_data]
CODE:00402103 E8 88 CD 00 00    call    j_RegQueryValueExA
CODE:00402108 85 C0             test    eax, eax
CODE:0040210A 0F 85 D9 01 00 00 jnz     loc_0_4022E9
CODE:00402110 33 C0             xor     eax, eax
CODE:00402112 EB 12             jmp     short loc_0_402126

In questo stralcio di codice ho già cambiato i nomi delle varie var_xx per comodità. Si evince quindi facilmente che l'app ha preparato un buffer di 8 byte, da riempire con le informazioni recuperate da 'HKEY_LOCAL_MACHINE\Software\Babylon\Babylon Translator\b1\IndexByData'. Sono dei valori esadecimali che per ora non ci dicono niente...

-# Fine Reversing Babylon Protection - Parte I #-

-# Considerazioni generali - I #-

L'analisi di un'applicazione da sproteggere non va fatta tutta d'un fiato... Una protezione va studiata ed analizzata a fondo, oltre che per il gusto di bypassarla, anche per capire dove il programmatore sbaglia, e, come dicono a Roma, errando discitur ;). Con uno strumenti potenti come IDA e Soft-Ice è davvero difficile ostacolare un possibile reverser.

Comunque, come dicevo, l'analisi di parti di codice va fatta in più passate, per capire meglio 'chi è cosa'. Nel listato del programma disassemblato ci sono una marea di variabili, che possono avere un significato, DEVONO avere un significato, tutto sta nel capire quale.

-# Fine considerazioni generali - I #-

-# Reversing Babylon Protection - Parte II #-

Dopo aver eseguito l'applicazione diverse volte, ho iniziato a dare i nomi alle variabili del programma, comprendendo qual è la 'filosofia' della protezione. Questa può esser suddivisa per comodità in due parti, una che controlla che non sia stata cambiata la data di sistema, e l'altra che calcola quanti giorni rimangono alla scadenza. I dati sulla scadenza sono contenuti nel registro di win sotto la chiave 'HKEY_LOCAL_MACHINE\Software\Babylon\Babylon Translator\b1\' e sono criptati. Non a caso l'applicazione utilizza due maschere XOR per leggerli, che vengono inizializzate all'inizio:

CODE:00402094 8B 05 38 23 42 00 mov     eax, ds:dword_0_422338 ;
CODE:0040209A 89 45 F8          mov     [ebp+Xor_value1], eax  ;<---\   Le due maschere XOR
CODE:0040209D 8B 05 3C 23 42 00 mov     eax, ds:dword_0_42233C ;   | \  La prima � utilizzata
CODE:004020A3 89 45 FC          mov     [ebp+var_4], eax       ;   |  > con la data corrente, la
CODE:004020A6 8B 15 40 23 42 00 mov     edx, ds:dword_0_422340 ;   | /  seconda con i dati sulla
CODE:004020AC 89 55 F4          mov     [ebp+Xor_value2], edx  ;<---/   scadenza

e sono usate prima per decriptare i byte e poi per criptarli nuovamente, a chiasmo (12|21). Così, IndexByData contiene le informazioni sulla data odierna e IndexByExport sui giorni rimanenti.

Con la seguente routine viene decriptata IndexByExport, e controllato che Day_Remaining sia >=0.

CODE:004021C0 EB 12             jmp     short Start_Xor2
CODE:004021C2                   Xor2:					 ; Routine che decripta
CODE:004021C2 0F BF D0          movsx   edx, ax				 ; IndexByExport usando
CODE:004021C5 8A 4C 15 F4       mov     cl, byte ptr [ebp+edx+Xor_value2]; la maschera XOR #2
CODE:004021C9 0F BF D0          movsx   edx, ax				 ; (Xor_value2)
CODE:004021CC 30 8C 15 64 FF FF+xor     [ebp+edx+buffer_address], cl	 ; buffer_addr = dati da 
CODE:004021D3 40                inc     eax				 ;  decriptare
CODE:004021D4                   Start_Xor2:				 ; buffer_size = dimens. 
CODE:004021D4 0F BF C8          movsx   ecx, ax				 ;  buffer (4 bytes)
CODE:004021D7 3B 4D EC          cmp     ecx, [ebp+buffer_size]		 ;
CODE:004021DA 72 E6             jb      short Xor2			 ;
CODE:004021DC 66 C7 45 F2 00 00 mov     [ebp+Day_Remaining], 0		 ; *** memcpy ***
CODE:004021E2 6A 01             push    1				 ; num. bytes da copiare
CODE:004021E4 8D 85 64 FF FF FF lea     eax, [ebp+buffer_address]	 ; origine
CODE:004021EA 50                push    eax				 ;
CODE:004021EB 8D 45 F2          lea     eax, [ebp+Day_Remaining]	 ; destinazione
CODE:004021EE 50                push    eax				 ;
CODE:004021EF E8 D4 31 00 00    call    _memcpy				 ; COPIA
CODE:004021F4 83 C4 0C          add     esp, 0Ch
CODE:004021F7 66 29 75 F2       sub     [ebp+Day_Remaining], si
CODE:004021FB 66 83 7D F2 00    cmp     [ebp+Day_Remaining], 0		 ; Se Day_Remaining � 
CODE:00402200 7D 06             jge     short Still_some_days		 ; >= 0, vai avanti

Si capisce che in IndexByExport solo il primo byte è quello significativo, quello che una volta decriptato dà il numero effettivo di giorni rimamenti, il resto dei bytes sono chiacchiere...

Subito dopo il salto, viene 'assemblata' una parola di 4 bytes, di cui il primo valore è quello dei giorni rimanenti. Poi viene salvata nuovamente nel registro...

CODE:00402208                   Still_some_days:
CODE:00402208 0F B7 95 E2 FE FF+movzx   edx, [ebp+Junk_value1]
CODE:0040220F C1 E2 10          shl     edx, 10h
CODE:00402212 0F B7 8D E0 FE FF+movzx   ecx, [ebp+Junk_value2]
CODE:00402219 C1 E1 08          shl     ecx, 8
CODE:0040221C 0B D1             or      edx, ecx
CODE:0040221E 0F BF 45 F2       movsx   eax, [ebp+Day_Remaining]
CODE:00402222 0B D0             or      edx, eax
CODE:00402224 89 55 E4          mov     [ebp+Jkval1_Jkval2_Day_Remaining], edx
CODE:00402227 FF 75 EC          push    [ebp+buffer_size]	; buffer_size = 4 bytes
CODE:0040222A 8D 55 E4          lea     edx, [ebp+Jkval1_Jkval2_Day_Remaining]
CODE:0040222D 52                push    edx
CODE:0040222E 8D 8D E4 FE FF FF lea     ecx, [ebp+XORed_Final_Value]
CODE:00402234 51                push    ecx
CODE:00402235 E8 8E 31 00 00    call    _memcpy
CODE:0040223A 83 C4 0C          add     esp, 0Ch
CODE:0040223D 33 C0             xor     eax, eax
CODE:0040223F EB 12             jmp     short Start_Xor3
CODE:00402241                   Xor3:				; Routine di encriptazione
CODE:00402241 0F BF D0          movsx   edx, ax
CODE:00402244 8A 4C 15 F4       mov     cl, byte ptr [ebp+edx+Xor_value2]
CODE:00402248 0F BF D0          movsx   edx, ax
CODE:0040224B 30 8C 15 E4 FE FF+xor     [ebp+edx+XORed_Final_Value], cl
CODE:00402252 40                inc     eax
CODE:00402253                   Start_Xor3:
CODE:00402253 0F BF C8          movsx   ecx, ax
CODE:00402256 3B 4D EC          cmp     ecx, [ebp+buffer_size]
CODE:00402259 72 E6             jb      short Xor3
CODE:0040225B FF 75 EC          push    [ebp+buffer_size]
CODE:0040225E 8D 85 E4 FE FF FF lea     eax, [ebp+XORed_Final_Value] ; Valore finale criptato
CODE:00402264 50                push    eax
CODE:00402265 6A 03             push    3
CODE:00402267 6A 00             push    0
CODE:00402269 68 CE 25 42 00    push    offset str->Indexbye_0
CODE:0040226E FF 75 E8          push    [ebp+Bab_Handle_of_open_HKey_data]
CODE:00402271 E8 20 CC 00 00    call    j_RegSetValueExA	      ; Salva valore finale

Il resto della protezione non fa più parte effettivamente della protezione, in quanto la parte critica è appena stata eseguita. La parte finale è:

CODE:004022F1 0F BF 45 F2       movsx   eax, [ebp+Day_Remaining]
CODE:004022F5 8B 55 08          mov     edx, [ebp+Addr_Day_Remaining]
CODE:004022F8 89 02             mov     [edx], eax
CODE:004022FA 85 DB             test    ebx, ebx              ; Se ebx=1, app scaduta
CODE:004022FC 74 04             jz      short loc_0_402302
CODE:004022FE 33 C0             xor     eax, eax
CODE:00402300 EB 04             jmp     short loc_0_402306
CODE:00402302                   loc_0_402302:
CODE:00402302 0F BF 45 F2       movsx   eax, [ebp+Day_Remaining]
CODE:00402306                   loc_0_402306:
CODE:00402306 5E                pop     esi
CODE:00402307 5B                pop     ebx
CODE:00402308 8B E5             mov     esp, ebp
CODE:0040230A 5D                pop     ebp
CODE:0040230B C2 04 00          retn    4

, e non fa altro che spostare i giorni rimanenti in un indirizzo che viene utilizzato per visualizzarli nel programma, nella dialog box di informazioni.

Ora che la protezione è stata spiegata del tutto sta a voi modificare il flusso del programma o cambiare i valori nel registro per avere il massimo dei giorni possibili. Questo numero non può superare i 255 giorni, poichè Day_Remaining è di 1 byte (FF=255).

-# Conclusione #-

Se mi è stato possibile analizzare nei particolari questa routine di protezione è stato grazie ad IDA. E' possibile scaricarlo dalla pagina di Iczelion (iczelion.cjb.net) e per utilizzarlo al meglio sarebbe bene leggere la essay di +mammon su fravia o sul suo sito stesso. Se riceverò abbastanza richieste farò la traduzione del 'manuale di Ida' di +mammon.

Includo anche l'intera procedura totalmente disassemblata e reversed qui.

 

Copyright (c) Little-John 1999

mail: little_john80(at)hotmail(dot)com